// Copyright 2024 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package db

import (
	"time"

	"cloud.google.com/go/spanner"
)

type Series struct {
	ID          string `spanner:"ID"`
	ExtID       string `spanner:"ExtID"`
	AuthorName  string `spanner:"AuthorName"`
	AuthorEmail string `spanner:"AuthorEmail"`
	Title       string `spanner:"Title"`
	Link        string `spanner:"Link"`
	Version     int64  `spanner:"Version"`
	// In LKML patches, there are often hints at the target tree for the patch.
	SubjectTags []string  `spanner:"SubjectTags"`
	PublishedAt time.Time `spanner:"PublishedAt"`
	// TODO: we could ger rid of the field by using slightly more complicated SQL queries.
	LatestSessionID spanner.NullString `spanner:"LatestSessionID"`
	Cc              []string           `spanner:"Cc"`
}

func (s *Series) SetLatestSession(session *Session) {
	s.LatestSessionID = spanner.NullString{StringVal: session.ID, Valid: true}
}

type Patch struct {
	ID       string `spanner:"ID"`
	Seq      int64  `spanner:"Seq"`
	SeriesID string `spanner:"SeriesID"`
	Title    string `spanner:"Title"`
	Link     string `spanner:"Link"`
	BodyURI  string `spanner:"BodyURI"`
}

type Build struct {
	ID         string             `spanner:"ID"`
	TreeName   string             `spanner:"TreeName"`
	TreeURL    string             `spanner:"TreeURL"`
	CommitHash string             `spanner:"CommitHash"`
	CommitDate time.Time          `spanner:"CommitDate"`
	SeriesID   spanner.NullString `spanner:"SeriesID"`
	Arch       string             `spanner:"Arch"`
	ConfigName string             `spanner:"ConfigName"`
	ConfigURI  string             `spanner:"ConfigURI"`
	LogURI     string             `spanner:"LogURI"`
	Status     string             `spanner:"Status"`
	Compiler   string             `spanner:"Compiler"`
}

func (b *Build) SetSeriesID(val string) {
	b.SeriesID = spanner.NullString{StringVal: val, Valid: true}
}

const (
	BuildFailed string = "build_failed"
	//	BuiltNotTested  string = "built"
	//	BuildTestFailed string = "tests_failed"
	BuildSuccess string = "success"
)

type Session struct {
	ID           string             `spanner:"ID"`
	SeriesID     string             `spanner:"SeriesID"`
	CreatedAt    time.Time          `spanner:"CreatedAt"`
	StartedAt    spanner.NullTime   `spanner:"StartedAt"`
	FinishedAt   spanner.NullTime   `spanner:"FinishedAt"`
	SkipReason   spanner.NullString `spanner:"SkipReason"`
	LogURI       string             `spanner:"LogURI"`
	TriageLogURI string             `spanner:"TriageLogURI"`
	Tags         []string           `spanner:"Tags"`
	// TODO: to accept more specific fuzzing assignment,
	// add Triager, BaseRepo, BaseCommit, Config fields.
}

type SessionStatus string

const (
	SessionStatusWaiting    SessionStatus = "waiting"
	SessionStatusInProgress SessionStatus = "in progress"
	SessionStatusFinished   SessionStatus = "finished"
	SessionStatusSkipped    SessionStatus = "skipped"
	// To be used in filters.
	SessionStatusAny SessionStatus = ""
)

// It could have been a calculated field in Spanner, but the Go library for Spanner currently
// does not support read-only fields.
func (s *Session) Status() SessionStatus {
	if s.StartedAt.IsNull() {
		return SessionStatusWaiting
	} else if s.FinishedAt.IsNull() {
		return SessionStatusInProgress
	} else if !s.SkipReason.IsNull() {
		return SessionStatusSkipped
	}
	return SessionStatusFinished
}

func (s *Session) Duration() time.Duration {
	if s.FinishedAt.IsNull() {
		return 0
	}
	return s.FinishedAt.Time.Sub(s.StartedAt.Time).Truncate(time.Minute)
}

func (s *Session) SetStartedAt(t time.Time) {
	s.StartedAt = spanner.NullTime{Time: t, Valid: true}
}

func (s *Session) SetFinishedAt(t time.Time) {
	s.FinishedAt = spanner.NullTime{Time: t, Valid: true}
}

func (s *Session) SetSkipReason(reason string) {
	s.SkipReason = spanner.NullString{StringVal: reason, Valid: true}
}

type SessionTest struct {
	SessionID           string             `spanner:"SessionID"`
	BaseBuildID         spanner.NullString `spanner:"BaseBuildID"`
	PatchedBuildID      spanner.NullString `spanner:"PatchedBuildID"`
	UpdatedAt           time.Time          `spanner:"UpdatedAt"`
	TestName            string             `spanner:"TestName"`
	Result              string             `spanner:"Result"`
	LogURI              string             `spanner:"LogURI"`
	ArtifactsArchiveURI string             `spanner:"ArtifactsArchiveURI"`
}

type Finding struct {
	ID              string           `spanner:"ID"`
	SessionID       string           `spanner:"SessionID"`
	TestName        string           `spanner:"TestName"`
	Title           string           `spanner:"Title"`
	ReportURI       string           `spanner:"ReportURI"`
	LogURI          string           `spanner:"LogURI"`
	SyzReproURI     string           `spanner:"SyzReproURI"`
	SyzReproOptsURI string           `spanner:"SyzReproOptsURI"`
	CReproURI       string           `spanner:"CReproURI"`
	InvalidatedAt   spanner.NullTime `spanner:"InvalidatedAt"`
}

func (f *Finding) SetInvalidatedAt(t time.Time) {
	f.InvalidatedAt = spanner.NullTime{Time: t, Valid: true}
}

type SessionReport struct {
	ID         string           `spanner:"ID"`
	SessionID  string           `spanner:"SessionID"`
	ReportedAt spanner.NullTime `spanner:"ReportedAt"`
	Moderation bool             `spanner:"Moderation"`
	Reporter   string           `spanner:"Reporter"`
}

func (s *SessionReport) SetReportedAt(t time.Time) {
	s.ReportedAt = spanner.NullTime{Time: t, Valid: true}
}

type ReportReply struct {
	MessageID string    `spanner:"MessageID"`
	ReportID  string    `spanner:"ReportID"`
	Time      time.Time `spanner:"Time"`
}

// BaseFinding collects all crashes observed on the base kernel tree.
// It will be used to avoid unnecessary bug reproduction attempts.
type BaseFinding struct {
	CommitHash string `spanner:"CommitHash"`
	Config     string `spanner:"Config"`
	Arch       string `spanner:"Arch"`
	Title      string `spanner:"Title"`
}
